﻿import os

from error import EndException
import opcode
from core import *
from wbox.il.asm import Assembler

def gettype(s):
    if s == None or len(s) < 3: # 채움 또는 끝
        return 0
    if s[0] != s[-1]:
        return -1 # 잘못된 코드
    code = ord(s[1])
    if 0xf0 <= code:
        return 3 # 확장 디바이스
    if code == 0x82 or 0x90 <= code:
        return 2 # 디바이스
    if code == 0x80:
        return 4
    return 1 # 명령

def getCommand(stream):
    s = stream.getNext()
    t = gettype(s)
    if t == 0:
        return
    if t != 1: # 명령
        raise Exception(u"명령이 필요합니다.")
    return s[1:-1]


class GxCommand(GxObject):
    def __init__(self, code, arguments=None, note=None):
        self.name, self.steps = opcode.getname(code)
        self.arguments = arguments
        self.note = note
        if self.note != None:
            self.steps += self.note.getNoteSteps()

    def _code(self):
        return ""
        return table[self.command]

    def append(self, device):
        self.arguments.append(device)

    def code(self):
        code = [table[self.command]]
        code += map(lambda x: x.code(), self.devices)
        return code

    def format(self, stream):
        stream.writecode(table[self.name])
        map(lambda x: x.format(stream), self.devices)

    @staticmethod
    def fromCode(stream):
        command = getCommand(stream)
        if not command:
            raise EndException()

        arguments = []
        note = None
        while stream.nextIsDevice():
            device = GxDevice.fromCode(stream)
            if device.isNote():
                note = device
            else:
                arguments.append(device)

        return GxCommand(command, arguments, note)

    def __str__(self):
        s = [self.name]
        s += map(str, self.arguments)
        if self.note:
            s += [str(self.note)]
        return ' '.join(s)
        
    def __repr__(self):
        return "<{0}:{1}>".format(self.__class__.__name__, self.name)
        

class GxDevice(list):
    def __init__(self, codes):
        list.__init__(self, codes)

    def isNote(self):
        return self[0][0] == 0x82
        
    def getNoteSteps(self):
        return self[0][1][0]
        
    def isBitDevice(self):
        return core.isBitDevice(self[-1][0])
        
    def isBitWord(self):
        return core.isBitDevice(self[-1][0]) and (self[0][0] == 0xF1)
        
    def isConstant(self):
        return core.isConstant(self[-1][0])
        
    def __str__(self):
        pre = []
        main = []
        post = []
        for code in self:
            try:
                name, address = getDeviceName(*code)
                if code[0] == 0x82:
                    main.append(";" + address[1])
                elif code[0] == 0xEE:
                    main.append('"' + address + '"')
                elif code[0] >= 0xF0: # 확장
                    if name in "UJ":
                        pre.append(name + address + "\\")
                    elif name == "K":
                        pre.append(name + address)
                    elif name == "@":
                        pre.append(name)
                    elif name in "ZV.":
                        post.append(name + address)
                else:
                    main.append(name + address)
            except KeyError:
                main.append("%02X:%d" % (code[0], code[1]))
        return "".join(pre + main + post)
        
    def __repr__(self):
        return "<%s:%s>" % (self.__class__.__name__, list.__repr__(self))
        
    @staticmethod
    def fromCode(stream):
        codes = [stream.getNextDevice()]
        while codes[-1][0] >= 0xF0 and stream.nextIsDevice():
            codes.append(stream.getNextDevice())
        return GxDevice(codes)
        

class GxStatement(GxObject):
    def __init__(self, comment):
        self.comment = comment
        self.steps = ord(self.comment[2])

    def __repr__(self):
        return "<%s:'%s'>" % (self.__class__.__name__, self.__str__())

    def __str__(self):
        if self.steps == 1:
            msg = "*"
        else:
            msg = self.comment[3:-1]
        return ";" + msg

    def code(self):
        comment_length = len(self.comment)
        if self.type == "*":
            comment_length = 1
        elif comment_length == 0:
            comment_length = 2
        return [0x80, comment_length] + map(ord, self.comment)

    def format(self, stream):
        stream.writecode(self.code())

    @staticmethod
    def fromCode(stream):
        comment = stream.getNext()
        if ord(comment[1]) != 0x80:
            raise Exception("주석이 아닙니다.")
        return GxStatement(comment)
        
        

class GxProgram(object):
    def __init__(self, project, path=None):
        self.header = None
        self._body = []
        self._src = []
        self.project = project
        if path != None:
            self.load(path)
        
    def loadHead(self, stream):
        self.header = GxHeader(stream.getNext())
        # program information: 
        #     ladder is empty. 04 00 FF FF
        #     sfc has block's device
        stream.readDataBlock()
        
    def loadLine(self, stream):
        tokenType = gettype(stream.LA(1))
        if tokenType == 4:
            return GxStatement.fromCode(stream)
        elif tokenType == 5:
            return GxNote.fromCode(stream)
        else:
            return GxCommand.fromCode(stream)

    def loadBody(self, stream):
        try:
            while True:
                line = self.loadLine(stream)
                self._body.append(line)
                self._src.append(str(line))
        except EndException:
            pass
        except:
            import traceback
            traceback.print_exc()
            raise

    def load(self, path):
        self.path = path
        
        #print "Program Name:", os.path.split(self.path)[1]
        f = open(self.path, "rb")
        stream = GxStream(f.read())
        f.close()

        self.loadHead(stream)
        self.loadBody(stream)
        
    def save_src(self, path=None):
        if not path:
            path = self.path
            
        f = BinaryWriter(path)
        f.write(self.header.data)
        f.word(0x0004)
        f.word(0xffff)
        
        asm = Assembler()
        il = asm.parse(self._src)
        L = []
        count = 0
        for line in il:
            for op in line:
                L.append(op)
                count += 1
        data = "".join(L)
        f.write(data)
        
        #length = len(data)
        #x = struct.pack("2L6s", length, count, "")
        #self.content = x + data
        #self.header.program = "WBOX_GEN"        
        
        f.close()
        
    def getLines(self, includeStepNo=True):
        L = []
        L.append("=== HEAD ===")
        L.append(str(self.header))
        L.append("=== BODY ===")
        stepNo = 0
        fmt = "{0:8} {1}" if includeStepNo else "{1}"
        for line in self._body:
            L.append(fmt.format(stepNo, line))
            stepNo += line.steps
        if includeStepNo:
            L.append("{0:8}".format(stepNo))
        return L
        
    def dump(self, path=None):
        return "\n".join(self.getLines())
            
    def __iter__(self):
        index = 0
        while True:
            try:
                x = yield self._body[index]
                if x:
                    index += x
                else:
                    index += 1
            except IndexError:
                raise StopIteration
